using System;
using System.Linq;
using NUnit.Framework;
using Tycho.Grammar;
using Tycho.Language;
using Tycho.Parser.Peg;

namespace Tycho.UnitTests.Grammar {
    [TestFixture]
    public class ParserSyntaxErrorFeature {
        [Test]
        public void ShouldRecoverFromSyntaxError () {
            AssertSyntaxError (
                @"alphabet {alpha: id beta: id ';'}",
                "a 8 ;",
                AssertSyntaxErrors (At (2, 1))
            );
        }

        [Test]
        public void ShouldNotRecoverFromSyntaxError () {
            AssertSyntaxError (
                @"alphabet {alpha: id beta: id ';'}",
                "8 a ;",
                AssertSyntaxErrors (At (0, 5))
            );
        }

        [Test]
        public void ShouldParseEntireSourceEvenWithSyntaxErrors () {
            AssertSyntaxError (
@"setter = setter {'set' flag: id}
choice {setter, id}",
                "set 6",
                AssertSyntaxErrors (At (4, 1))
            );
        }

        [Test, Ignore]
        public void ShouldTryAllPossibleRecoveryPoints () {
            AssertSyntaxError (
@"choice
    definition {left: id '=' right: int}
    plus {left: id '+' right: int}
    divide {left: id '/' right: int}",
                "7 + 3",
                AssertSyntaxErrors (At (0, 1))
            );
        }

        [Test]
        public void ShouldRescueDelimitedGrammar () {
            AssertSyntaxError (
@"integers {i: int ',' ...}",
                "1, a, b, 4",
                AssertSyntaxErrors (At (3, 1), At (6, 1))
            );
        }

        private static ErrorLocation At (int index, int length) {
            return new ErrorLocation (index, length);
        }

        private static Action<SyntaxException> AssertSyntaxErrors (params ErrorLocation [] errorLocations) {
            return (se) => {
                Assert.That (se.SyntaxErrors.Count (), Is.EqualTo (errorLocations.Length));

                int n = 0;
                foreach (var location in errorLocations) {
                    SourceInformation sinfo = se.SyntaxErrors.ElementAt (n).Term.SourceInformation;
                    Assert.That (sinfo, Is.EqualTo (new SourceInformation (null, location.StartIndex, location.Length)));
                    n++;
                }
            };
        }

        class ErrorLocation {
            public readonly int StartIndex;
            public readonly int Length;

            public ErrorLocation (int startIndex, int length) {
                StartIndex = startIndex;
                Length = length;
            }
        }

        private static void AssertSyntaxError (string grammar, string source, Action<SyntaxException> assertSyntaxException) {
            IParser parser = CompileGrammar (grammar);

            try {
                ITerm term = parser.ParseTerm (source);
                Assert.Fail ("expected: " + typeof (SyntaxException));
            } catch (SyntaxException se) {
                assertSyntaxException (se);
            }
        }

        private static IParser CompileGrammar (string source) {
            return new ParserLoader ().LoadParser (source);
        }
    }
}